import fs from "fs";
import path from "path";
import { entityPath, javaPackage, javaRoot, jpr, mapperPath, serviceImplPath, servicePath, root } from "./config";
const tables: string[] = [];
function uc(s: string) {
  return s.replace(/_(\w)/i, m => { return m.substring(m.length - 1).toUpperCase(); });
}
function fu(s: string) {
  return s.substring(0, 1).toUpperCase() + s.substring(1);
}
function hump(p: string) {
  return fu(uc(p));
}
function writeMapper(tableName: string, comment: string) {
  const mp = path.resolve(mapperPath, `${tableName}Mapper.java`),
    xp = path.resolve(mapperPath, `xml/${tableName}Mapper.xml`);
  if (!fs.existsSync(mp)) {
    const mapper = `package ${javaPackage}.mapper;

import ${javaPackage}.entity.${tableName};
import ${jpr}.api.Util;

/**
 * ${comment} Mapper 接口
 * 
 * @author mac-xiang
 */
public interface ${tableName}Mapper extends Util.BaseMapper<${tableName}> {

}`;
    fs.writeFileSync(mp, mapper);
  }
  if (!fs.existsSync(xp)) {
    const xml = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="${javaPackage}.mapper.${tableName}Mapper">

</mapper>
`;
    fs.writeFileSync(xp, xml);
  }
}
function readServiceImpl(tableName: string, comment: string) {
  const p = path.resolve(serviceImplPath, `${tableName}ServiceImpl.java`);
  let ret = "";
  if (fs.existsSync(p)) {
    ret = fs.readFileSync(p, "utf8");
  } else {
    ret = `package ${javaPackage}.service.impl;

import java.util.List;

import jakarta.annotation.Resource;

import org.springframework.stereotype.Service;

import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import ${jpr}.api.Util;
import ${javaPackage}.entity.User;
import ${javaPackage}.entity.${tableName};
import ${javaPackage}.mapper.${tableName}Mapper;
import ${javaPackage}.service.${tableName}Service;

/**
 * ${comment} 服务实现类
 * 
 * @author mac-xiang
 */
@Service
public class ${tableName}ServiceImpl extends ServiceImpl<${tableName}Mapper, ${tableName}> implements ${tableName}Service {
  @Resource
  ${tableName}Mapper mapper;

  private QueryWrapper<${tableName}> readWrapper(JSONObject p, User per) {
    QueryWrapper<${tableName}> ret = Util.getQuery(p, ${tableName}.class);
    if (per != null && !per.per("${comment}查询")) {
      // ret.eq("\`create\`", per.id);
    }
    return ret;
  }

  private List<${tableName}> getList(JSONObject p, User per) {
    return mapper.selectList(readWrapper(p, per));
  }

  private Page<${tableName}> getPage(Page<${tableName}> page, JSONObject p, User per) {
    return mapper.selectPage(page == null ? new Page<${tableName}>(1, 100) : page, readWrapper(p, per));
  }

  public ${tableName} create(JSONObject p, User per) {
    if (per != null && per.per("${comment}创建")) {
      ${tableName} entity = Util.getEntity(p, ${tableName}.class, per.id);
      // entity.createTime = null;
      // entity.create = per.id;
      mapper.insert(entity);
      return entity;
    }
    throw new RuntimeException("没有权限");
  }

  public int update(JSONObject p, User per) {
    if (per != null && per.per("${comment}修改")) {
      ${tableName} entity = Util.getEntity(p, ${tableName}.class);
      QueryWrapper<${tableName}> qw = Util.getQuery(p, ${tableName}.class);
      if (this.count(qw) > 10) {
        throw new RuntimeException("单次修改数量超过10条");
      }
      // entity.create = null;
      return mapper.update(entity, qw);
    }
    throw new RuntimeException("没有权限");
  }

  public Object read(JSONObject p, User per) {
    Page<${tableName}> page = Util.getPage(p, ${tableName}.class);
    return page == null ? getList(p, per) : getPage(page, p, per);
  }

  public int delete(JSONObject p, User per) {
    if (per != null && per.per("${comment}删除")) {
      QueryWrapper<${tableName}> q = Util.getQuery(p, ${tableName}.class);
      if (this.count(q) > 10) {
        throw new RuntimeException("删除数据的数量不能超过10条");
      }
      return mapper.delete(q);
    }
    throw new RuntimeException("没有权限");
  }

  public long count(JSONObject p, User per) {
    return this.count(readWrapper(p, per));
  }
}
`;
    fs.writeFileSync(p, ret, "utf8");
  }
  return ret;
}
function writeService(tableName: string, comment: string) {
  const p = path.resolve(servicePath, `${tableName}Service.java`),
    si = readServiceImpl(tableName, comment),
    ret = [`package ${javaPackage}.service;

import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.extension.service.IService;
import ${javaPackage}.entity.User;`],
    rc: string[] = [];
  si.match(/\n  public [^ ]+ \w+\([^\{}]+/g)?.forEach(s => {
    s = s.substring(1);
    s.substring(9).replace(/\s+[\w_]+[\(\),]/g, " ").replace(/[<>]/g, " ").split(" ").forEach(type => {
      if (type) {
        const [t] = si.match(new RegExp(`import [\\w\\.]+\\.${type};`)) || [];
        if (t && !ret.includes(t) && ret[0].indexOf(t) < 0) {
          ret.push(t);
        }
      }
    });

    rc.push(s.replace(/(?:\bpublic\s+)|(?:\s$)/g, "") + ";");
  });
  ret.push(`\npublic interface ${tableName}Service extends IService<${tableName}> {\n`, rc.join("\n\n"), "}");
  const d = ret.join("\n");
  if (fs.existsSync(p) && fs.readFileSync(p, { encoding: "utf8" }) == d) {
    return;
  }
  fs.writeFileSync(p, d);
}
function writeStaticMapper() {
  const
    staticMapperPath = path.resolve(javaRoot, "StaticMapper.java"),
    frist = [`package ${javaPackage};\n`,
      "import org.springframework.beans.factory.annotation.Autowired;",
      "import org.springframework.stereotype.Component;"],
    middle = ["\n@Component", "public class StaticMapper {"],
    content: string[] = [];
  tables.forEach(e => {
    frist.push(`import ${javaPackage}.mapper.${e}Mapper;`);
    const el = e.charAt(0).toLowerCase() + e.substring(1);
    content.push(`
  public static ${e}Mapper ${el};

  @Autowired
  public void set${e}(${e}Mapper param) {
    ${el} = param;
  }`);
  });
  frist.push(...middle, ...content, "}");
  const r = frist.join("\n");
  if (!fs.existsSync(staticMapperPath) || r != fs.readFileSync(staticMapperPath, "utf8").replace(/\r/g, "")) {
    fs.writeFileSync(staticMapperPath, r, "utf8");
  }
}
function writeStaticService() {
  const
    staticServicePath = path.resolve(javaRoot, "StaticService.java"),
    frist = [`package ${javaPackage};\n`,
      "import java.util.List;",
      "import org.springframework.beans.factory.annotation.Autowired;",
      "import org.springframework.stereotype.Component;",
      "import com.xiang.api.Util;"],
    middle = `
@Component
public class StaticService {

  public static <T> List<T> selectSql(String sql, Class<T> clazz) {
    return Util.toArray(Util.toJString(StaticSql.run.select(sql)), clazz);
  }`,
    content: string[] = [];
  tables.forEach(e => {
    frist.push(`import ${javaPackage}.service.${e}Service;`);
    const el = e.charAt(0).toLowerCase() + e.substring(1);
    content.push(`
  public static ${e}Service ${el};

  @Autowired
  public void set${e}(${e}Service param) {
    ${el} = param;
  }`);
  });
  frist.push(middle, ...content, "}");
  const r = frist.join("\n");
  // console.log(r == fs.readFileSync(staticServicePath, "utf8").replace(/\r/g, ""));
  if (!fs.existsSync(staticServicePath) || r != fs.readFileSync(staticServicePath, "utf8").replace(/\r/g, "")) {
    fs.writeFileSync(staticServicePath, r, "utf8");
  }
}
function writeService2(tableName: string, comment: string) {
  const p = path.resolve(servicePath, `${tableName}Service.java`);
  if (!fs.existsSync(p)) {
    const ret = `package ${javaPackage}.service;

import java.util.List;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import ${jpr}.api.Util;
import ${javaPackage}.entity.User;
import ${javaPackage}.entity.${tableName};
import ${javaPackage}.mapper.${tableName}Mapper;

/**
 * ${comment} 服务类
 * 
 * @author mac-xiang
 */
@Service
public class ${tableName}Service extends Util.BaseService<${tableName}Mapper, ${tableName}> {
}
`;
    fs.writeFileSync(p, ret, "utf8");
  }
}
export function apiSql(/** 文件完整路径 */p: string) {
  tables.splice(0);
  fs.readFileSync(p, { encoding: "utf8" }).replace(/\r/ig, "").match(/CREATE TABLE [^;]+/gm)?.forEach(s => {
    const [, table, tableComment] = s.match(/CREATE TABLE `(\w+)`[\s\S]+comment\s*=\s*'(.+)'\s*engine\s*=/i) as string[],
      tableName = fu(uc(table)),
      entityFilePath = path.resolve(entityPath, `${tableName}.java`);

    if (!fs.existsSync(entityFilePath)) {
      const entity = `package ${javaPackage}.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableField;
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * ${tableComment} 数据实体
 * 
 * @author mac-xiang
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName(value = "\`${table}\`", autoResultMap = true)
@ApiModel(value = "${tableComment} 对象", description = "${tableComment}")
public class ${tableName} implements Serializable {

  private static final long serialVersionUID = 1L;
`,
        res: string[] = [];
      s.match(/`(\w+)`\s+(\w+)[^\n]+/g)?.forEach(ts => {
        const name = (ts.match(/`(\w+)`/)?.[1]) as string,
          tsl = ts.toLowerCase(),
          type = tsl.indexOf("int") > 0 ? (tsl.indexOf("unsigned") > 0 ? "Long" : "Integer") : "String",
          comment = ts.match(/comment\s+'([^']+)/i)?.[1] || name,
          def = ts.match(/default\s+'?([^'\s]+)/i)?.[1] || "",
          f = name.toLowerCase() != "id",
          ins = def ? "" : (f ? ", fill = FieldFill.INSERT" : ", type = IdType.ASSIGN_ID");
        res.push(`  /** ${comment} */
  @Table${f ? "Field" : "Id"}(value = "${f ? `\`${name}\`` : name}"${ins})
  public ${type} ${uc(name)};`);
      });
      fs.writeFileSync(entityFilePath, entity + res.join("\n\n") + `\n\n  public ${tableName}() {\n  }\n}`);
      writeMapper(tableName, tableComment);
      writeService2(tableName, tableComment);
    }
    tables.push(tableName);
  });
  tables.sort();
  writeStaticMapper();
  writeStaticService();
  // console.log(tables);
}

export function jssic(/** 文件完整路径 */p: string,) {
  let a = p.lastIndexOf("\\"), b = p.lastIndexOf("/");
  if (a < b) { a = b; }
  a++; b = p.lastIndexOf("ServiceImpl.java");
  const s = p.substring(a, b),
    c = fs.readFileSync(p, { encoding: "utf8" }).match(/\*\s+([^\s]+)\s+服务实现类/)?.[1];
  if (c) {
    writeService2(s, c);
  } else {
    console.error("没有找到注释", p);

  }
}

export function ts(tableName?: string, tsi?: any) {
  let ret = "";
  if (tableName) {
    tableName = uc(tableName);
    try {
      fs.readFileSync(path.resolve(root, "api/default.sql"), { encoding: "utf8" }).replace(/\r/ig, "").match(/CREATE TABLE [^;]+/gm)?.forEach(s => {
        const [, table, tableComment] = s.match(/CREATE TABLE `(\w+)`[\s\S]+comment\s*=\s*'([^']+)'/i) as string[],
          tn = uc(table);
        if (tn === tableName) {
          const a: string[] = [`\n/** ${tableComment} */`, `  interface ${tn} extends obj {`],
            b: string[] = [`/** ${tableComment} */`, `    get ${tn}(): sql.${tn} {`, `      return {`],
            c: string[] = [`/** ${tableComment} */`, `  async ${tn}(p?: any): Promise<any> {
    if (Array.isArray(p)) {
      await Promise.all(p.map(async (v) => { await this.${tn}(v); }));
      return p;
    } else if (this.isPages(p)) {
      await this.${tn}(p.data);
      return p;
    } else if (p) {
      this.setDef(p, this.def.${tn});
      return p;
    }
  };`],
            d: string[] = [`/** ${tableComment} */`, `    ${tn}: new BaseArrSimple("${tn}", format.${tn}.bind(format)),`];

          s.match(/`(\w+)`\s+(\w+)[^\n]+/g)?.forEach(ts => {
            const [, n, typeSource, comment] = ts.match(/`(\w+)` (.*) comment '(.*)'/i) || [];
            if (n) {
              const name = uc(n),
                type = /int/i.test(typeSource) ? "number" : "string",
                d = ts.match(/default\s+'?([^'\s]+)/i)?.[1] || "",
                def = type === "number" ? Number(d) || 0 : `"${d}"`;
              a.push(`/** ${comment} ${typeSource} */`, `    ${name}: ${type};`);
              b.push(`/** ${comment} ${typeSource} */`, `        ${name}: ${def},`);
            }
          });
          a.push(`  }`);
          b.push(`      };`, `    },`);
          ret = [a.join("\n"), b.join("\n"), c.join("\n"), d.join("\n")].join("\n\n\n");
          throw null;
        }
      });
      console.log("未找到");
    } catch (e: any) {
      e && console.error(e);
    }
  }
  if (ret) {
    import("clipboardy").then(c => {
      c.default.writeSync(ret);
      process.exit(0);
    });
  } else { process.exit(0); }
}
export function tsa() {
  const sqlArr = fs.readFileSync(path.resolve(root, "api/default.sql"), { encoding: "utf8" }).replace(/\r/ig, "").match(/CREATE TABLE [^;]+/gm) || [],
    ret = {
      def: ['export * from "./apiUrl";', 'export * from "../public/module/baseFormat";', 'import { per } from "./apiUrl";', 'export const def = {'],
      format: ['export * from "./def";', 'import { ImplFromat, BaseFormat, def } from "./def";\n', 'class Format extends BaseFormat implements ImplFromat {', '  constructor () { super(def); }\n'],
      gd: ['export * from "./format";', 'import { BaseArrSimple, format } from "./format";\n', 'export const gd: gt.gl.Gd = {'],
      sqld: ["namespace sql {"],
      sqlNames: ["/** 主要是后台可能需要 */", "export const sqlNames = {"],
      tableColumns: ["export const tableColumns: { [k in gt.gl.SqlK]: readonly gt.components.ElTableColumn[] } = {"],
    };
  for (const s of sqlArr) {
    const [, table, tableComment] = s.match(/CREATE TABLE `(\w+)`[\s\S]+comment\s*=\s*'([^']+)'/i) as string[],
      tn = uc(table);
    const a: string[] = [`\n  /** ${tableComment} */`, `  interface ${tn} extends obj {`],
      b: string[] = [`  /** ${tableComment} */`, `  get ${tn}(): sql.${tn} {`, `    return {`],
      c: string[] = [`  /** ${tableComment} */`, `  async ${tn}(p?: any): Promise<any> {
    if (Array.isArray(p)) {
      await Promise.all(p.map(async (v) => { await this.${tn}(v); }));
      return p;
    } else if (this.isPages(p)) {
      await this.${tn}(p.data);
      return p;
    } else if (p) {
      this.setDef(p, this.def.${tn});
      return p;
    }
  };`], tableColumn = [`\n  /** ${tableComment} */`, `  get ${tn}() {`, "    return ["],
      d: string[] = [`  /** ${tableComment} */`, `  ${tn}: new BaseArrSimple("${tn}", format.${tn}.bind(format)),`];

    s.match(/`(\w+)`\s+(\w+)[^\n]+/g)?.forEach(ts => {
      const [, n, typeSource, comment] = ts.match(/`(\w+)` (.*) comment '(.*)'/i) || [];
      if (n) {
        const name = uc(n),
          type = /int/i.test(typeSource) ? "number" : "string",
          d = ts.match(/default\s+'?([^'\s]+)/i)?.[1] || "",
          def = type === "number" ? Number(d) || 0 : `"${d}"`;
        a.push(`    /** ${comment} ${typeSource} */`, `    ${name}: ${type};`);
        b.push(`      /** ${comment} ${typeSource} */`, `      ${name}: ${def},`);
        tableColumn.push(`      /** ${comment} ${typeSource} */`, `      { prop: "${name}", label: "${comment}" },`);
      }
    });
    a.push(`  }`);
    b.push(`    };`, `  },`);
    tableColumn.push("    ];\n  },");

    ret.def.push(b.join("\n"));
    ret.format.push(c.join("\n"));
    ret.gd.push(d.join("\n"));
    ret.sqld.push(a.join("\n"));
    ret.sqlNames.push(`  get ${tn}() { return "${tableComment}" as const; },`);
    ret.tableColumns.push(tableColumn.join("\n"));
    // break;
  }
  ret.def.push("} as const;\n");
  ret.format.push("}\nexport const format = new Format();\n");
  ret.gd.push("} as const;", "format.setGd.bind(format)(gd);\n");
  ret.sqld.push("}\nexport = sql;", "export as namespace sql;\n");
  ret.sqlNames.push("} as const;\n");
  ret.tableColumns.push("};\n");

  fs.writeFileSync(path.resolve(root, "dataBase/def.ts"), ret.def.join("\n"));
  fs.writeFileSync(path.resolve(root, "dataBase/format.ts"), ret.format.join("\n"));
  fs.writeFileSync(path.resolve(root, "dataBase/gd.ts"), ret.gd.join("\n"));
  fs.writeFileSync(path.resolve(root, "dataBase/sql.d.ts"), ret.sqld.join("\n"));
  fs.writeFileSync(path.resolve(root, "dataBase/sqlNames.ts"), ret.sqlNames.join("\n"));
  fs.writeFileSync(path.resolve(root, "dataBase/tableColumns.ts"), ret.tableColumns.join("\n"));


  // import("clipboardy").then(c => {
  //   c.default.writeSync(ret.tableColumns.join("\n"));
  //   process.exit(0);
  // });
  console.log("生成完成");
  process.exit(0);
}
function sql2javaType(sqlType: string, unsigned?: any): JavaType {
  const map: obj = {
    binary: 0,
    varbinary: 0,
    tinyblob: 0,
    blob: 0,
    mediumblob: 0,
    longblob: 0,
    boolean: 1,
    tinyint: 1,
    smallint: 2,
    mediumint: 3,
    int: 3,
    integer: 3,
    bigint: 4,
    float: 6,
    double: 7,
    precision: 7,
    decimal: 8,
    numeric: 8
  }, jta: JavaType[] = ["byte[]", "Byte", "Short", "Integer", "Long", "BigInteger", "Float", "Double", "BigDecimal", "String", "String"];
  let i = map[sqlType.toLowerCase()];
  if (!Number.isFinite(i)) {
    i = 9;
  }
  if (unsigned) {
    i++;
  }
  return jta[i];
}

/**根据sql文件生成新版实体类文件;
 * @param sqlPath sql文件路径
 * @param name    实体类名;不传则全部查找并生成不存在的实体类
 */
export function genEntity(sqlPath: string, name?: string) {
  const sqlParse = parseSql(sqlPath);
  if (name) {
    name = fu(uc(name));
    if (Object.prototype.hasOwnProperty.call(sqlParse, name)) {
      const e = sqlParse[name];
      writeEntity(e, true);
    }
  } else {
    for (const entityName in sqlParse) {
      if (Object.prototype.hasOwnProperty.call(sqlParse, entityName)) {
        const e = sqlParse[entityName];
        console.log(entityName);
        writeEntity(e);
      }
    }
  }
}
/**解析Sql文件
 * @param sqlPath sql文件路径
 * @returns 
 */
function parseSql(sqlPath: string) {
  const ret: sqlParse = {};
  fs.readFileSync(sqlPath, { encoding: "utf8" }).replace(/\r/ig, "").match(/CREATE TABLE [^;]+/gm)?.forEach(s => {
    const [, tableName, comment] = s.match(/CREATE TABLE `(\w+)`[\s\S]+comment\s*=\s*'(.+)'\s*engine\s*=/i) as string[],
      entityName = fu(uc(tableName)),
      entityFilePath = path.resolve(entityPath, `${entityName}.java`),
      primaryKey = (s.match(/PRIMARY KEY[\s\(`]+(\w+)/)?.[1]) as string,
      fields: SqlParseField[] = [];
    s.match(/`(\w+)`\s+(\w+)[^\n,]+/g)?.forEach(source => { // 解析行
      const sqlName = (source.match(/`(\w+)`/)?.[1]) as string,
        field = uc(sqlName),
        comment = source.match(/comment\s+'([^']+)/i)?.[1] || field,
        def = source.match(/default\s+'?([^'\s]+)/i)?.[1] || "",
        type = sql2javaType(source.match(/` ([\w]+)/i)?.[1] || "", /\bunsigned\b/i.test(source));
      fields.push({
        field, sqlName, isPrimary: field == primaryKey,
        type: field == "other" ? "JSONObject" : type,
        comment, def, source
      });
    });
    ret[entityName] = { name: entityName, path: entityFilePath, primaryKey, tableName, comment, fields };
  });
  return ret;
}
/** 写数据到Entity
 * @param entity  解析Sql后的实体数据格式
 * @param force   是否强制写入;文件不存在,或此值为真的情况下,会写入文件
 */
function writeEntity(entity: SqlParseEntity, force?: any) {
  if (force || !fs.existsSync(entity.path)) {
    const s = entity.fields.map(e => `  /** ${e.comment}\n   * ${e.source}\n   */
  @TableField(value = "${e.sqlName}"${e.def && e.field != "createTime" ? "" :
        (", insertStrategy = FieldStrategy." + (e.isPrimary
          ? (e.type == "String" ? "STR32" : "LONG")
          : (e.type == "String"
            ? "EMPTY_STRING"
            : (e.sqlName.indexOf("time") < 0
              ? "ZERO" : "TIMESTAMP"
            )
          )
        ))
      })
  public ${e.type} ${e.field};\n`);
    const result = `package ${javaPackage}.entity;\n
import java.io.Serializable;\n
import com.gitee.tmkmk._Enum.FieldStrategy;
import com.gitee.tmkmk.annotation.TableField;
import com.gitee.tmkmk.annotation.TableName;\n
import lombok.Data;
import lombok.EqualsAndHashCode;\n
/** ${entity.comment} 数据实体
 * @author mac-xiang
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName(value = "${entity.tableName}", primaryKey = "${entity.primaryKey}")
public class ${entity.name} implements Serializable {\n${s.join("\n")}
  public ${entity.name}() {
  }
\n}\n`;

    fs.writeFileSync(entity.path, result);
  }
}